#include "Log.h"

Log::Log()
{
    lineCount_ = 0;
    isAsync_ = false;
    writeThread_ = nullptr;
    Que_ = nullptr;
    toDay_ = 0;
    fp_ = nullptr;
}

Log::~Log()
{
    if (writeThread_ && writeThread_->joinable())
    {
        while (!Que_->empty())
            Que_->flush();
        flush();
        Que_->Close();
        writeThread_->join();
    }
    if (fp_)
    {
        std::unique_lock<std::mutex> lock(Mtx_);
        flush();
        fclose(fp_);
    }
}

Log *Log::Instance()
{
    static Log log;
    return &log;
}

int Log::GetLevel()
{
    std::lock_guard<std::mutex> lock(Mtx_);
    return level_;
}

void Log::SetLevel(int level)
{
    std::lock_guard<std::mutex> lock(Mtx_);
    level_ = level;
}

void Log::FlushLogThread()
{
    Log::Instance()->AsyncWrite();
}

void Log::AsyncWrite()
{
    std::string str = " ";
    while (Que_->Pop(str))
    {
        std::lock_guard<std::mutex> locker(Mtx_);
        fputs(str.c_str(), fp_);
    }
}

bool Log::IsOpen()
{
    return isOpen_;
}

void Log::AppendLogLevelTitle_(int level)
{
    switch (level)
    {
    case 0:
        buff_.Append("[debug]: ", 9);
        break;
    case 1:
        buff_.Append("[info] : ", 9);
        break;
    case 2:
        buff_.Append("[warn] : ", 9);
        break;
    case 3:
        buff_.Append("[error]: ", 9);
        break;
    default:
        buff_.Append("[info] : ", 9);
        break;
    }
}

void Log::flush()
{
    if (isAsync_)
        Que_->flush();
    fflush(fp_);
}

void Log::init(int level, const char *path,
               const char *suffix,
               int maxQueueCapacity)
{
    isOpen_ = true;
    level_ = level;
    if (maxQueueCapacity > 0)
    {
        isAsync_ = true;
        if (!Que_)
        {
            std::unique_ptr<BlockQueue<std::string>> que(new BlockQueue<std::string>);
            Que_ = std::move(que);
            std::unique_ptr<std::thread> thread(new std::thread(FlushLogThread));
            writeThread_ = std::move(thread);
        }
    }
    else
        isAsync_ = false;

    lineCount_ = 0;

    time_t now = time(nullptr);
    struct tm *systime = localtime(&now);
    struct tm t = *systime;
    path_ = path;
    suffix_ = suffix;

    char fileName[LOG_NAME_LEN] = {0};
    snprintf(fileName, LOG_NAME_LEN - 1, "%s/%04d_%02d_%02d%s",
             path_, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, suffix_);
    toDay_ = t.tm_mday;

    {
        std::lock_guard<std::mutex> lock(Mtx_);
        buff_.ClearBuffer();
        if (fp_)
        {
            flush();
            fclose(fp_);
        }
        fp_ = fopen(fileName, "a");
        if (fp_ == nullptr)
        {
            std::cout << fileName << std::endl;
            int op = mkdir(fileName, 0777);
            std::cout<<op<<std::endl;
            fp_ = fopen(fileName, "a");
        }
        assert(fp_ != nullptr);
    }
}

void Log::write(int level, const char *format, ...)
{
    struct timeval now = {0, 0};
    gettimeofday(&now, nullptr);
    time_t tSec = now.tv_sec;
    struct tm *sysTime = localtime(&tSec);
    struct tm t = *sysTime;
    va_list vaList;
    if (t.tm_mday != toDay_ && (lineCount_ && lineCount_ % MAX_LINES == 0))
    {
        char tail[36] = {0};
        char fileName[LOG_NAME_LEN] = {0};
        snprintf(tail, LOG_NAME_LEN - 72, "%04d_%02d_%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);
        if (t.tm_mday != toDay_)
        {
            snprintf(fileName, LOG_NAME_LEN - 72, "%s/%s%s", path_, tail, suffix_);
            toDay_ = t.tm_mday;
            lineCount_ = 0;
        }
        else
        {
            snprintf(fileName, LOG_NAME_LEN - 72, "%s/%s-%d%s", path_, tail, lineCount_ / MAX_LINES, suffix_);
        }
        std::unique_lock<std::mutex> lock(Mtx_);
        flush();
        fclose(fp_);
        fp_ = fopen(fileName, "a");
        assert(fp_ != nullptr);
    }
    {
        std::unique_lock<std::mutex> locker(Mtx_);
        lineCount_++;
        int n = snprintf(buff_.BeginWrite(), 128, "%d-%02d-%02d %02d:%02d:%02d.%06ld ",
                         t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
                         t.tm_hour, t.tm_min, t.tm_sec, now.tv_usec);
        AppendLogLevelTitle_(level);
        va_start(vaList, format);
        int m = vsnprintf(buff_.BeginWrite(), buff_.WriteAble(), format, vaList);
        va_end(vaList);
        buff_.HasWritten(m);
        buff_.Append("\n\0", 2);
        if (isAsync_ && !Que_->full())
            Que_->Push(buff_.ClearBufferToStr());
        else
            fputs(buff_.Peek(), fp_);
        buff_.ClearBuffer();
    }
}